import {render} from '@testing-library/react-native';
import {Str} from 'expensify-common';
import {Linking} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
// eslint-disable-next-line no-restricted-imports, no-restricted-syntax
import * as AppActions from '@libs/actions/App';
import {hasAuthToken, signOutAndRedirectToSignIn} from '@libs/actions/Session';
// eslint-disable-next-line no-restricted-imports, no-restricted-syntax
import * as Session from '@libs/actions/Session';
import {getCurrentUserEmail, setLastShortAuthToken} from '@libs/Network/NetworkStore';
import App from '@src/App';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {createRandomReport} from '../utils/collections/reports';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
import waitForNetworkPromises from '../utils/waitForNetworkPromises';

jest.mock('@libs/BootSplash', () => ({
    hide: jest.fn().mockResolvedValue(undefined),
}));

jest.mock('@libs/Navigation/AppNavigator/usePreloadFullScreenNavigators', () => jest.fn());

const TEST_USER_ACCOUNT_ID_1 = 123;
const TEST_USER_LOGIN_1 = 'test@test.com';
// cspell:disable-next-line
const TEST_AUTH_TOKEN_1 = 'asdfghjkl';

const TEST_USER_ACCOUNT_ID_2 = 456;
const TEST_USER_LOGIN_2 = 'test2@test.com';
// cspell:disable-next-line
const TEST_AUTH_TOKEN_2 = 'zxcvbnm';

jest.setTimeout(120000);
TestHelper.setupApp();
TestHelper.setupGlobalFetchMock();

const report = createRandomReport(7, undefined);

function getInitialURL() {
    const params = new URLSearchParams();

    params.set('exitTo', `${ROUTES.REPORT}/${report.reportID}`);
    params.set('email', TEST_USER_LOGIN_1);
    params.set('shortLivedAuthToken', TEST_AUTH_TOKEN_1);

    const deeplinkUrl = `${CONST.DEEPLINK_BASE_URL}/transition?${params.toString()}`;
    return deeplinkUrl;
}

describe('Deep linking', () => {
    let lastVisitedPath: string | undefined;
    let originalSignInWithShortLivedAuthToken: typeof Session.signInWithShortLivedAuthToken;
    let originalOpenApp: typeof AppActions.openApp;

    beforeAll(() => {
        originalSignInWithShortLivedAuthToken = Session.signInWithShortLivedAuthToken;
        originalOpenApp = AppActions.openApp;
    });

    beforeEach(() => {
        Onyx.connect({
            key: ONYXKEYS.LAST_VISITED_PATH,
            callback: (val: OnyxEntry<string>) => (lastVisitedPath = val),
        });

        jest.spyOn(Session, 'signInWithShortLivedAuthToken').mockImplementation(() => {
            Onyx.merge(ONYXKEYS.CREDENTIALS, {
                login: TEST_USER_LOGIN_1,
                autoGeneratedLogin: Str.guid('expensify.cash-'),
                autoGeneratedPassword: Str.guid(),
            });
            Onyx.merge(ONYXKEYS.ACCOUNT, {
                validated: true,
                isUsingExpensifyCard: false,
            });
            Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
                [TEST_USER_ACCOUNT_ID_1]: TestHelper.buildPersonalDetails(TEST_USER_LOGIN_1, TEST_USER_ACCOUNT_ID_1, 'Test'),
            });
            Onyx.merge(ONYXKEYS.SESSION, {
                authToken: TEST_AUTH_TOKEN_1,
                accountID: TEST_USER_ACCOUNT_ID_1,
                email: TEST_USER_LOGIN_1,
                encryptedAuthToken: TEST_AUTH_TOKEN_1,
            });
            Onyx.merge(ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID, 'randomID');

            return originalSignInWithShortLivedAuthToken(TEST_AUTH_TOKEN_1);
        });

        jest.spyOn(AppActions, 'openApp').mockImplementation(async () => {
            const originalResult = await originalOpenApp();
            await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report);
            return originalResult;
        });
    });

    afterEach(async () => {
        await Onyx.clear();
        await waitForNetworkPromises();
        jest.clearAllMocks();
        lastVisitedPath = undefined;
        Linking.setInitialURL('');
        setLastShortAuthToken(null);
    });

    it('should not remember the report path of the last deep link login after signing out and in again', async () => {
        expect(hasAuthToken()).toBe(false);

        const url = getInitialURL();
        // User signs in automatically when the app is rendered because of the deep link
        Linking.setInitialURL(url);
        const {unmount} = render(<App />);

        await waitForBatchedUpdates();

        expect(lastVisitedPath).toBe(`/${ROUTES.REPORT}/${report.reportID}`);

        expect(hasAuthToken()).toBe(true);

        signOutAndRedirectToSignIn();

        await waitForBatchedUpdatesWithAct();

        expect(hasAuthToken()).toBe(false);

        await TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID_2, TEST_USER_LOGIN_2, undefined, TEST_AUTH_TOKEN_2);

        await waitForBatchedUpdatesWithAct();

        expect(lastVisitedPath).toBeDefined();
        expect(lastVisitedPath).not.toBe(`/${ROUTES.REPORT}/${report.reportID}`);

        unmount();
        await waitForBatchedUpdatesWithAct();
    });

    it('should not reuse the last deep link and log in again when signing out', async () => {
        expect(hasAuthToken()).toBe(false);

        const {unmount: unmount1} = render(<App />);
        await TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID_2, TEST_USER_LOGIN_2, undefined, TEST_AUTH_TOKEN_2);

        await waitForBatchedUpdatesWithAct();

        expect(hasAuthToken()).toBe(true);
        expect(getCurrentUserEmail()).toBe(TEST_USER_LOGIN_2);
        // Unmount so we can prepare the deep link login
        unmount1();

        await waitForBatchedUpdatesWithAct();

        const url = getInitialURL();
        // User signs in automatically when the app is remounted because of the deep link.
        // This overrides the previous sign-in.
        Linking.setInitialURL(url);
        const {unmount: unmount2} = render(<App />);

        await waitForBatchedUpdatesWithAct();

        expect(getCurrentUserEmail()).toBe(TEST_USER_LOGIN_1);

        signOutAndRedirectToSignIn();

        await waitForBatchedUpdatesWithAct();

        // In a failing scenario, remounting triggers the sign-in with the deep link again because it still remembers it.
        // However, we've implemented a fix so that it does not reuse the last deep link.
        unmount2();
        const {unmount: unmount3} = render(<App />);

        await waitForBatchedUpdatesWithAct();

        expect(hasAuthToken()).toBe(false);

        unmount3();
        await waitForBatchedUpdatesWithAct();
    });
});
